import type { App } from 'vue'
import utils from './src/utils.js'
import { isProducton } from '@/config/env.config'
import loggerConfig from '@/config/logger_config/index'
import { DETAIL_TYPE_CONFIG } from './src/customInfo'

interface LoggerConfigAction {
	id: string
	label: string
	messageHandler: (params: object) => string
}

interface ThirdMenu {
	id: string
	label: string
	billGenerateTypeKey: string
	actions: LoggerConfigAction[]
	[key: string]: LoggerConfigAction[] | LoggerConfigAction | string
}

interface SubMenu {
	id: string
	label: string
	billGenerateTypeKey: string
	actions: LoggerConfigAction[]
	child: ThirdMenu[]
	[key: string]: LoggerConfigAction[] | LoggerConfigAction | string | ThirdMenu[] | ThirdMenu
}

interface MainMenu {
	id: string
	label: string
	child: SubMenu[]
	[key: string]: SubMenu | SubMenu[] | string
}

type LoggerConfig = MainMenu[]

interface FxLogger {
	[key: string]: unknown
}

interface OrderDetail {
	billType: string | number
}

interface LibraryItem {
	id: string
	handler?: Function
	label?: string
	type?: string
}

interface DiffItem<T> {
	pre: T
	cur: T
}

interface OrganItem {
	id: string,
	name: string
}

interface BillItem {
	id: string
	sysBillCode: string
	parentBill?: {
		id: string
		sysBillCode: string
		billClass: string
	};
	parentBillType?: string
	otherOrgan?: OrganItem
}

interface UserInfo {
	username: string
	tenantId: number
	employeeName: string
	organName: string
	userId: string
	hostId: string
}

interface CustomInfo {
	[key: string]: string | number | boolean
}

interface ActionInfo {
	mainMenu: string
	subMenu: string
	thirdMenu: string
	actionType: string
	message: string
}

interface LoggerConfigParams {
	defaultInfo: {
		platformType: string
	}
	customInfo: {
		billType: number
	}
	app: string
	remote: string
}
const Log4jsApi = window.Log4js
const R_QUOTE = /"/g

const getLoggerConfigMap = function (config: LoggerConfig) {
	const configMap: { [key: string]: MainMenu } = {}
	config.forEach(mainMenu => {
		if (Array.isArray(mainMenu.child)) {
			mainMenu.child.forEach(subMenu => {
				if (Array.isArray(subMenu.actions)) {
					subMenu.actions.forEach(action => {
						subMenu[action.id] = action
					})
				}
				if (Array.isArray(subMenu.child)) {
					subMenu.child.forEach(thirdMenu => {
						if (Array.isArray(thirdMenu.actions)) {
							thirdMenu.actions.forEach(action => {
								thirdMenu[action.id] = action
							})
						}
						subMenu[thirdMenu.id] = thirdMenu
					})
				}
				mainMenu[subMenu.id] = subMenu
			})
		}
		configMap[mainMenu.id] = mainMenu
	})
	return configMap
}

const loggerConfigMap = getLoggerConfigMap(loggerConfig as LoggerConfig)
class FxLogger {
	public appVue: App
	public curUUID: string
	public curUser: string | null
	public tenantId: number
	public userId: string
	public employeeName: string
	public organName: string
	public hostId: string
	public domain: string | undefined
	public trans: number
	public used: number
	public mainMenu: string
	public subMenu: string
	public thirdMenu: string
	public actionType: string
	public resulteType: string | number
	public errorCause: string
	public defaultCustomInfo: unknown
	public customInfo: unknown
	constructor (config: LoggerConfigParams, app: App) {
		this.initDefaultInfo(config)
		this.initCustomInfo(config)
		this.appVue = app
		this.curUUID = utils.getUUID()
		this.curUser = null
		this.tenantId = 0
		this.userId = ''
		this.employeeName = ''
		this.organName = ''
		this.hostId = ''
		this.domain = utils.getDomain()
		this.used = 0
		this.trans = 0
		this.mainMenu = ''
		this.subMenu = ''
		this.thirdMenu = ''
		this.actionType = ''
		this.resulteType = ''
		this.errorCause = ''
		const _this = this as FxLogger
		const Logger = Log4jsApi.Logger
		const JSONLayout = Log4jsApi.JSONLayout
		const layoutFormat = JSONLayout.prototype.format
		JSONLayout.prototype.format = function (e: unknown) {
			const event = e as Record<string, unknown>
			event.message = JSON.stringify({
				..._this.getDefaultInfoObj(),
				..._this.formatCustomeInfoByEvent(event),
				sid: _this.curUUID,
				uid: _this.curUser || '*',
				userId: _this.userId,
				tenantId: _this.tenantId,
				hostId: _this.hostId,
				domain: _this.domain,
				userEmployeeName: _this.employeeName,
				userOrganName: _this.organName,
				used: event.used,
				trans: event.trans,
				mainMenu: event.mainMenu,
				subMenu: event.subMenu,
				thirdMenu: event.thirdMenu,
				actionType: event.actionType,
				resulteType: event.resulteType,
				logTime: event.logTime,
				userLogTime: event.userLogTime,
				message: event.message
			}).replace(/\\/g, '\\\\').replace(R_QUOTE, '\\"')
			return layoutFormat.call(this, e)
		}
		const fGetEvent = Logger.prototype.getEvent
		Logger.prototype.getEvent = function (logLevel: string, message: string, exception: string) {
			const res = fGetEvent.call(this, logLevel, message, exception)
			const logTime = utils.setDateTime(utils.newDate(), 'HH:MM:SS')
			const userLogTime = parseInt(logTime.replace(':', '').replace(':', ''))
			_this.setCustomerInfoByInstance(res)
			res.used = _this.used
			res.trans = _this.trans
			res.mainMenu = _this.mainMenu
			res.subMenu = _this.subMenu
			res.thirdMenu = _this.thirdMenu
			res.actionType = _this.actionType
			res.resulteType = _this.resulteType
			res.domain = _this.domain
			res.logTime = logTime
			res.userLogTime = userLogTime
			_this.used = 0
			_this.trans = 0
			_this.mainMenu = ''
			_this.subMenu = ''
			_this.thirdMenu = ''
			_this.actionType = ''
			_this.resulteType = ''
			return res
		}
	}

	// logger初始化
	init (user: { username: string }) {
		this.actionMessage('系统准备就绪')
		if (user.username) {
			this.setUserInfo(user as UserInfo)
		}
		this.bindErrorLogger()
	}

	bindErrorLogger () {
		if (isProducton()) {
			this.appVue.config.errorHandler = (error) => {
				this.runtimeInfo(error as string, '', 0)
			}
		} else {
			window.onerror = (error) => {
				this.runtimeInfo(error as string, '', 0)
			}
		}
	}

	// 设置用户
	setUserInfo ({ username, tenantId, employeeName, organName, userId, hostId }: UserInfo) {
		this.curUser = username
		this.tenantId = tenantId
		this.employeeName = `${employeeName}`
		this.organName = `${organName}`
		this.userId = `${userId}`
		this.hostId = `${hostId}`
		this.domain = utils.getDomain()
		this.actionMessage(`授权账号: ${username}`)
	}

	// actionInfo信息采集
	actionInfo ({ mainMenu, subMenu, thirdMenu, actionType, message, ...args }: ActionInfo) {
		const start = utils.getTime()
		const end = (resulteType: number) => {
			const end = utils.getTime()
			this.used = end - start
			this.mainMenu = mainMenu
			this.subMenu = subMenu
			this.thirdMenu = thirdMenu
			this.actionType = actionType
			this.resulteType = resulteType
			this.setCustomeInfo2Instance(args)
		}
		const success = () => {
			const resulteType = 1
			end.call(this, resulteType)
		}
		const error = () => {
			const resulteType = 0
			end.call(this, resulteType)
		}
		return {
			end: end.bind(this),
			success: success.bind(this),
			error: error.bind(this)
		}
	}

	// actionMessage信息采集
	actionMessage (message: string) {
	}

	// runtime信息采集
	runtimeInfo (errmsg: string, uri: string, row: number) {
		this.runtimeLogger.info(`err:${errmsg} uri:${uri || '-'} row: ${row}`)
	}

	// request信息采集
	requestInfoStart (message: string, data: unknown, query: unknown) {
		const trans = utils.getUUIDInt()
		const start = utils.getTime()
		const _data = data ? JSON.stringify(data) : '-'
		const _query = query ? JSON.stringify(query) : '-'
		const end = function (this: {
			trans: number
			used: number
			requestLogger: { info (message: string): void }
		}, url: string, res: unknown, error: string) {
			// const _res = res ? JSON.stringify(res) : '-'
			const _error = error ? JSON.stringify(error) : '-'
			const end = utils.getTime()
			this.used = end - start
			this.trans = trans
			this.requestLogger.info(`B:${url} E: ${_error}`)
		}
		this.trans = trans
		this.requestLogger.info(`A:${message} P: ${_data} Q: ${_query}`)
		return {
			end: end.bind(this)
		}
	}

	initInfoHandler (config: LoggerConfigParams, infoName: keyof LoggerConfigParams) {
		this[infoName] = config[infoName]
		if (this[infoName]) {
			const infoObject = this[infoName] as FxLogger
			for (const key in infoObject) {
				this[key] = infoObject[key]
			}
		}
	}

	getInfoHandler (infoName: string) {
		const info: Record<string, unknown> = {}
		if (this[infoName]) {
			const infoObject = this[infoName] as FxLogger
			for (const key in infoObject) {
				info[key] = this[key]
			}
		}
		return info
	}

	initDefaultInfo (config: LoggerConfigParams) {
		this.initInfoHandler(config, 'defaultInfo')
	}

	getDefaultInfoObj () {
		return this.getInfoHandler('defaultInfo')
	}

	initCustomInfo (config: LoggerConfigParams) {
		this.initInfoHandler(config, 'customInfo')
		this.defaultCustomInfo = config.customInfo || {}
	}

	setCustomeInfo2Instance (args: CustomInfo = {}) {
		if (this.customInfo) {
			const defaultCustomInfo = this.defaultCustomInfo as CustomInfo
			for (const key in this.customInfo) {
				this[key] = args[key] !== undefined ? args[key] : defaultCustomInfo[key]
			}
		}
	}

	setCustomerInfoByInstance (res: CustomInfo) {
		if (this.customInfo) {
			for (const key in this.customInfo) {
				if (utils.isDef(this[key])) {
					res[key] = this[key] as string | number | boolean
				}
			}
		}
	}

	formatCustomeInfoByEvent (event: Record<string, unknown>): Record<string, unknown> {
		const info: Record<string, unknown> = {}
		if (this.customInfo) {
			for (const key in this.customInfo) {
				if (utils.isDef(event[key])) {
					info[key] = event[key]
				}
			}
		}
		return info
	}

	// 菜单信息采集
	menuInfo (message: string) {
		this.actionMessage(`选择[${message}]菜单`)
	}

	// 按钮信息采集
	btnInfo (message: string) {
		this.actionMessage(`单击[${message}]按钮`)
	}

	// 窗口信息采集
	dialogOpenInfo (message: string) {
		this.actionMessage(`打开[${message}]窗口`)
	}

	dialogCloseInfo (message: string) {
		this.actionMessage(`关闭[${message}]窗口`)
	}

	// 创建日志信息Params
	createInfo (action: string, params: object, customInfo = {}) {
		const actions = action.split('.')
		const mainMenu = actions[0]
		const subMenu = actions[1]
		let thirdMenu = ''
		let actionType = ''
		let message = ''
		if (actions.length === 3) {
			actionType = actions[2]
			message = ((loggerConfigMap[mainMenu][subMenu] as SubMenu)[actionType] as LoggerConfigAction).messageHandler.call(this, params)
		} else {
			thirdMenu = actions[2]
			actionType = actions[3]
			message = ((((loggerConfigMap[mainMenu][subMenu] as SubMenu)[thirdMenu] as ThirdMenu)[actionType] as LoggerConfigAction)).messageHandler.call(this, params)
		}
		const customLoggerInfo = Object.values(customInfo).reduce((prev: object, cur) => {
			return Object.assign(prev, cur)
		}, {})
		return {
			mainMenu,
			subMenu,
			thirdMenu,
			actionType,
			message,
			...customLoggerInfo
		}
	}

	// 添加单据详情自定义日志参数
	createDetailCustomInfo (types: (keyof typeof DETAIL_TYPE_CONFIG)[], orderDetail: OrderDetail) {
		return types.reduce((prev, cur) => {
			if (DETAIL_TYPE_CONFIG[cur]) {
				return Object.assign(prev, DETAIL_TYPE_CONFIG[cur](orderDetail))
			}
			return prev
		}, {})
	}

	// 创建差异日志文本
	createUpdateMessageByLibrary (library: LibraryItem[]) {
		return (pre: unknown[], cur: unknown[]) => {
			if (!utils.compare(pre, cur)) {
				const message: { [key: string]: string[] } = {}
				library.forEach((item) => {
					const preVal: unknown[] = utils.getObjDeepValue(pre, item.id)
					const curVal: unknown[] = utils.getObjDeepValue(cur, item.id)
					if (!utils.compare(preVal, curVal)) {
						let _message = ''
						if (typeof item.handler === 'function') {
							_message = item.handler.apply(this, [{ pre: preVal, cur: curVal, preFull: pre, curFull: cur }])
						} else {
							const preValText = utils.isDef(preVal) ? preVal : '无'
							const curValText = utils.isDef(curVal) ? curVal : '无'
							_message = `${item.label}由"${preValText}"改为"${curValText}"`
						}
						const type = item.type || 'default'
						if (!message[type]) {
							message[type] = []
						}
						message[type].push(_message)
					}
				})
				return Object.keys(message).map(item => message[item].join(',\n'))
			} else {
				return '数据未发生改变'
			}
		}
	}

	createMessages<T> (params: T[], func: (param: T) => string) {
		return params.map(func).join(',')
	}

	createUpdateMessage<T> (items: DiffItem<T>[], msgHandler: (param: T) => string) {
		return items.map(item => `${msgHandler(item.pre)} -> ${msgHandler(item.cur)}`).join(', ')
	}

	createDiffMessage<T> (
		{ label = '', pre, cur, msgHandler, getKeyFn }: {
			label?: string
			pre: string[]
			cur: string[]
			msgHandler: (param: T) => string
			getKeyFn: (param: T) => void
		}
	) {
		const data = utils.compareDiff(pre, cur, getKeyFn) as {
			add: T[]
			delete: T[]
			update: DiffItem<T>[]
		}
		const addMessage: string = data.add
			? `新增${label}: "${this.createMessages(data.add, msgHandler)}"`
			: ''
		const delMessage: string = data.delete
			? `删除${label}: "${this.createMessages(data.delete, msgHandler)}"`
			: ''
		const updateMessage: string = data.update
			? `修改${label}: ${this.createUpdateMessage(data.update, msgHandler)}`
			: ''
		return [addMessage, delMessage, updateMessage].filter(Boolean).join(',\n')
	}

	createCodeSpan (billData: BillItem[] | BillItem, billType: string) {
		let codeSpan = ''
		if (Array.isArray(billData)) {
			codeSpan = billData.map(item => {
				return item.id ? `<span class="fx-delegate-go" data-type="${billType}" data-billid="${item.id}">${item.sysBillCode}</span>` : item.sysBillCode
			}).join(',')
		} else {
			const { sysBillCode, id } = billData
			codeSpan = id ? `<span class="fx-delegate-go" data-type="${billType}" data-billid="${id}">${sysBillCode}</span>` : sysBillCode
		}
		return codeSpan
	}

	createDisableCodeSpan (billData: BillItem[], billType: string) {
		let codeSpan = ''
		if (Array.isArray(billData)) {
			codeSpan = billData.map(item => {
				return `<span class="fx-log-code__disable" data-type="${billType}" data-billid="${item.id}">${item.sysBillCode}</span>`
			}).join(',')
		} else {
			const { sysBillCode, id } = billData
			codeSpan = `<span class="fx-log-code__disable" data-type="${billType}" data-billid="${id}">${sysBillCode}</span>`
		}
		return codeSpan
	}
}

export type CreateCodeSpanFirstArg = Parameters<FxLogger['createCodeSpan']> extends [infer First, ...unknown[]] ? First : never

export default FxLogger
